// --------------------------------------------------------------------------------------------------------------------
// <copyright file="Package.cs" company="In The Hand Ltd">
//   Copyright (c) 2013-15 In The Hand Ltd, All rights reserved.
// </copyright>
// <summary>
//   Provides information about an app package.
// </summary>
// --------------------------------------------------------------------------------------------------------------------

namespace InTheHand.ApplicationModel
{
    using System;
    using System.IO;
    using System.Reflection;
    using System.Threading.Tasks;
    using Windows.ApplicationModel;
#if !SILVERLIGHT
    using Windows.Data.Xml.Dom;
#endif
    using Windows.Storage;
    using Windows.UI;

    /// <summary>
    /// Provides information about an app package. 
    /// </summary>
    public sealed class Package
    {
        private static Package current;

        private static bool isEightPointOne = false;

        /// <summary>
        /// Gets the package for the current app.
        /// </summary>
        public static Package Current
        {
            get
            {
                if (current == null)
                {
                    current = new Package(Windows.ApplicationModel.Package.Current);
                }

                return current;
            }
        }

        private Windows.ApplicationModel.Package nativeCurrent;

        private Package(Windows.ApplicationModel.Package native)
        {
            nativeCurrent = native;

#if SILVERLIGHT
            // parse the package xml to get attributes
            System.Xml.XmlReader xr = System.Xml.XmlReader.Create("WMAppManifest.xml");
            if (xr.ReadToDescendant("Deployment"))
            {
                // if built as an 8.1 app defer to platform for most properties
                isEightPointOne = new Version(xr["AppPlatformVersion"]) > new Version(8, 0);


                if (isEightPointOne)
                {
                    this.Id = new PackageId(nativeCurrent.Id);
                }
                else
                {
                    this.Id = new PackageId();
                }


                if (xr.ReadToDescendant("App"))
                {
                    if (!isEightPointOne)
                    {
                        Id.FullName = xr["ProductID"];
                    }
                    DisplayName = xr["Title"];
                    Description = xr["Description"];

                    if (DisplayName.StartsWith("@"))
                    {
                        try
                        {
                            object[] attributes = System.Reflection.Assembly.Load(System.Windows.Deployment.Current.EntryPointAssembly).GetCustomAttributes(typeof(System.Reflection.AssemblyProductAttribute), false);
                            if (attributes.Length > 0)
                            {
                                System.Reflection.AssemblyProductAttribute productAttribute = attributes[0] as System.Reflection.AssemblyProductAttribute;
                                if (productAttribute != null)
                                {
                                    DisplayName = productAttribute.Product;
                                }
                            }
                        }
                        catch
                        {
                            // will fail from background task
                        }
                    }


                    if (!isEightPointOne)
                    {
                        this.Id.Name = this.DisplayName;
                        this.Id.Version = new Version(xr["Version"]).ToPackageVersion();
                    }
                     
                    this.Id.Author = xr["Author"];
                        
                    

                    string isBeta = xr["IsBeta"];
                    if (!string.IsNullOrEmpty(isBeta))
                    {
                        this.IsBeta = bool.Parse(isBeta);
                    }

                    this.PublisherDisplayName = xr["Publisher"];

                    if (!isEightPointOne)
                    {
                        this.Id.Publisher = PublisherDisplayName;
                        this.Id.PublisherId = xr["PublisherID"];
                    }

                    if (xr.ReadToDescendant("IconPath"))
                    {
                        Logo = ImagePathToUri(xr);
                    }

                    if (xr.ReadToFollowing("Capabilities"))
                    {
                        bool more = xr.ReadToDescendant("Capability");
                        while (more)
                        {
                            Capability newCapability = (Capability)0;
                            DeviceCapability newDeviceCapability = (DeviceCapability)0;
                            switch (xr["Name"])
                            {
                                case "ID_CAP_NETWORKING":
                                    newCapability = Capability.InternetClientServer;
                                    break;
                                case "ID_CAP_IDENTITY_DEVICE":
                                    newCapability = Capability.IdentityDevice;
                                    break;
                                case "ID_CAP_IDENTITY_USER":
                                    newCapability = Capability.IdentityUser;
                                    break;
                                case "ID_FUNCCAP_EXTEND_MEM":
                                    newCapability = Capability.ExtendedMemory;
                                    break;
                                case "ID_CAP_LOCATION":
                                    newDeviceCapability = DeviceCapability.Location;
                                    break;
                                case "ID_CAP_SENSORS":
                                    newDeviceCapability = DeviceCapability.Sensors;
                                    break;
                                case "ID_CAP_MICROPHONE":
                                    newDeviceCapability = DeviceCapability.Microphone;
                                    break;
                                //case "ID_CAP_MEDIALIB":
                                //    newCapability = Capability.MediaLibrary;
                                //    break;
                                case "ID_CAP_MEDIALIB_AUDIO":
                                    newCapability = Capability.MusicLibrary;
                                    break;
                                case "ID_CAP_MEDIALIB_PHOTO":
                                    newCapability = Capability.PicturesLibrary;
                                    break;
                                case "ID_CAP_MEDIALIB_PLAYBACK":
                                    newCapability = Capability.MusicLibraryPlayback;
                                    break;
                                case "ID_CAP_GAMERSERVICES":
                                    newCapability = Capability.GamerServices;
                                    break;
                                case "ID_CAP_PHONEDIALER":
                                    newCapability = Capability.PhoneDialer;
                                    break;
                                case "ID_CAP_PUSH_NOTIFICATION":
                                    newCapability = Capability.PushNotification;
                                    break;
                                case "ID_CAP_REMOVABLE_STORAGE":
                                    newCapability = Capability.RemovableStorage;
                                    break;
                                case "ID_CAP_WEBBROWSERCOMPONENT":
                                    newCapability = Capability.WebBrowserComponent;
                                    break;
                                case "ID_CAP_RINGTONE_ADD":
                                    newCapability = Capability.RingtoneAdd;
                                    break;
                                case "ID_CAP_PROXIMITY":
                                    newDeviceCapability = DeviceCapability.Proximity;
                                    break;
                                case "ID_CAP_SPEECH_RECOGNITION":
                                    newCapability = Capability.SpeechRecognition;
                                    break;
                                case "ID_CAP_VOIP":
                                    newCapability = Capability.Voip;
                                    break;

                                case "ID_CAP_WALLET":
                                    newCapability = Capability.Wallet;
                                    break;
                                case "ID_CAP_WALLET_PAYMENTINSTRUMENTS":
                                    newCapability = Capability.WalletPaymentInstruments;
                                    break;
                                case "ID_CAP_WALLET_SECUREELEMENT":
                                    newCapability = Capability.WalletSecureElement;
                                    break;

                                case "ID_CAP_APPOINTMENTS":
                                    newCapability = Capability.Appointments;
                                    break;
                                case "ID_CAP_CONTACTS":
                                    newCapability = Capability.Contacts;
                                    break;
                                case "ID_CAP_ISV_CAMERA":
                                    newDeviceCapability = DeviceCapability.Camera;
                                    break;

                            }

                            System.Diagnostics.Debug.WriteLine((int)newCapability);
                            Capabilities |= newCapability;
                            DeviceCapabilities |= newDeviceCapability;
                            more = xr.ReadToNextSibling("Capability");
                        }
                    }
                }
            }

            xr.Dispose();

#else

#if WINDOWS_APP
            this.Id = Windows.ApplicationModel.Package.Current.Id;
#else
            this.Id = new PackageId(Windows.ApplicationModel.Package.Current.Id);
#endif

            Task t = Task.Run(async () =>
                {
                    Uri u = new Uri("ms-appx:///AppxManifest.xml");
                    var file = await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(u);
                    var stream = await file.OpenStreamForReadAsync();
                    var streamReader = new StreamReader(stream);
                    var xml = await streamReader.ReadToEndAsync();
                    XmlDocument xdoc = new XmlDocument();
                    xdoc.LoadXml(xml);

                    //parse xml
                    IXmlNode nameNode = xdoc.SelectSingleNodeNS("/appx:Package/appx:Properties/appx:DisplayName", "xmlns:appx=\"http://schemas.microsoft.com/appx/2010/manifest\"");
                    DisplayName = nameNode.InnerText;
                    IXmlNode publisherNode = xdoc.SelectSingleNodeNS("/appx:Package/appx:Properties/appx:PublisherDisplayName", "xmlns:appx=\"http://schemas.microsoft.com/appx/2010/manifest\"");
                    PublisherDisplayName = publisherNode.InnerText;

                    IXmlNode visualElementsNode = xdoc.SelectSingleNodeNS("/appx:Package/appx:Applications/appx:Application/m3:VisualElements", "xmlns:appx=\"http://schemas.microsoft.com/appx/2010/manifest\" xmlns:m3=\"http://schemas.microsoft.com/appx/2014/manifest\"");
                    if(visualElementsNode == null)
                    {
                        visualElementsNode = xdoc.SelectSingleNodeNS("/appx:Package/appx:Applications/appx:Application/m2:VisualElements", "xmlns:appx=\"http://schemas.microsoft.com/appx/2010/manifest\" xmlns:m2=\"http://schemas.microsoft.com/appx/2013/manifest\"");
                    }

                    if (visualElementsNode != null)
                    {
                        IXmlNode toastCapableNode = visualElementsNode.Attributes.GetNamedItem("ToastCapable");
                        if (toastCapableNode != null)
                        {
                            bool toastCapable = bool.Parse(toastCapableNode.InnerText);
                            if (toastCapable)
                            {
                                Capabilities |= Capability.PushNotification;
                            }
                        }

                        IXmlNode bgColorNode = visualElementsNode.Attributes.GetNamedItem("BackgroundColor");
                        if (bgColorNode != null)
                        {
                            string color = bgColorNode.InnerText;
                            if (color.StartsWith("#"))
                            {
                                // hex color
                                if (color.Length == 7)
                                {
                                    var r = byte.Parse(color.Substring(1, 2), System.Globalization.NumberStyles.HexNumber);
                                    var g = byte.Parse(color.Substring(3, 2), System.Globalization.NumberStyles.HexNumber);
                                    var b = byte.Parse(color.Substring(5, 2), System.Globalization.NumberStyles.HexNumber);
                                    BackgroundColor = Color.FromArgb(0xff, r, g, b);
                                }
                                else if (color.Length == 9)
                                {
                                    var a = byte.Parse(color.Substring(1, 2), System.Globalization.NumberStyles.HexNumber);
                                    var r = byte.Parse(color.Substring(3, 2), System.Globalization.NumberStyles.HexNumber);
                                    var g = byte.Parse(color.Substring(5, 2), System.Globalization.NumberStyles.HexNumber);
                                    var b = byte.Parse(color.Substring(7, 2), System.Globalization.NumberStyles.HexNumber);
                                    BackgroundColor = Color.FromArgb(a, r, g, b);
                                }
                            }
                            else
                            {
                                // color name
                                foreach (PropertyInfo pi in typeof(Colors).GetRuntimeProperties())
                                {
                                    if (pi.Name.ToLower() == color)
                                    {
                                        BackgroundColor = (Color)pi.GetValue(null);
                                        break;
                                    }
                                }
                            }
                        }

                        Description = visualElementsNode.Attributes.GetNamedItem("Description").InnerText;
                    }

#if !WINDOWS_APP
                    IXmlNode productIdNode = xdoc.SelectSingleNodeNS("/appx:Package/mp:PhoneIdentity", "xmlns:appx=\"http://schemas.microsoft.com/appx/2010/manifest\" xmlns:mp=\"http://schemas.microsoft.com/appx/2014/phone/manifest\"");
                    //Id.ProductId = productIdNode.Attributes.GetNamedItem("PhoneProductId").InnerText;
#endif    
                    IXmlNode logoNode = xdoc.SelectSingleNodeNS("/appx:Package/appx:Properties/appx:Logo", "xmlns:appx=\"http://schemas.microsoft.com/appx/2010/manifest\"");
                    Logo = new Uri("ms-appx:///" + logoNode.InnerText.Replace("\\", "/"));

                    IXmlNode capabilitiesNode = xdoc.SelectSingleNodeNS("/appx:Package/appx:Capabilities", "xmlns:appx=\"http://schemas.microsoft.com/appx/2010/manifest\"");
                    foreach(IXmlNode element in capabilitiesNode.ChildNodes)
                    {
                        if (element.NodeType == NodeType.ElementNode)
                        {
                            IXmlNode capNode = element.Attributes.GetNamedItem("Name");
                            string cap = capNode.InnerText;

                            switch(cap)
                            {
                                case "appointments":
                                    Capabilities |= Capability.Appointments;
                                    break;
                                case "contacts":
                                    Capabilities |= Capability.Contacts;
                                    break;
                                

                                case "musicLibrary":
                                    Capabilities |= Capability.MusicLibrary;
                                    break;
                                case "picturesLibrary":
                                    Capabilities |= Capability.PicturesLibrary;
                                    break;
                                case "videosLibrary":
                                    Capabilities |= Capability.VideosLibrary;
                                    break;


                                case "internetClient":
                                    Capabilities |= Capability.InternetClientServer;
                                    break;

                                case "internetClientServer":
                                    Capabilities |= Capability.InternetClientServer;
                                    break;


                                case "location":
                                    DeviceCapabilities |= DeviceCapability.Location;
                                    break;

                                case "proximity":
                                    DeviceCapabilities |= DeviceCapability.Proximity;
                                    break;

                                case "microphone":
                                    DeviceCapabilities |= DeviceCapability.Microphone;
                                    break;

                                case "webcam":
                                    DeviceCapabilities |= DeviceCapability.Camera;
                                    break;


                                case "removableStorage":
                                    Capabilities |= Capability.RemovableStorage;
                                    break;


                                case "enterpriseAuthentication":
                                    Capabilities |= Capability.EnterpriseAuthentication;
                                    break;

                                case "sharedUserCertificates":
                                    Capabilities |= Capability.SharedUserCertificates;
                                    break;
                            }
                        }
                    }
                    
                });
            t.Wait();
#endif
        }

        /// <summary>
        /// Returns the background color of the application's primary tile.
        /// </summary>
        public Color BackgroundColor
        {
            get;
            private set;
        }

        /// <summary>
        /// Returns a collection of Capabilities requested for the application.
        /// </summary>
        [CLSCompliant(false)]
        public Capability Capabilities
        {
            get;
            private set;
        }

        /// <summary>
        /// Returns a collection of DeviceCapabilities requested for the application.
        /// </summary>
        [CLSCompliant(false)]
        public DeviceCapability DeviceCapabilities
        {
            get;
            private set;
        }
        
        /// <summary>
        /// Gets the description of the package.
        /// </summary>
        public string Description
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the display name of the package.
        /// </summary>
        public string DisplayName
        {
            get;
            private set;
        }

        /// <summary>
        /// Gets the package identity of the current package.
        /// </summary>
        /// <value>The package identity.</value>
        public PackageId Id
        {
            get; private set;
        }

        /// <summary>
        /// Gets the date the application package was installed on the user's phone.
        /// </summary>
        public DateTimeOffset InstallDate
        {
            get
            {
                // using the date the folder was created (on initial install)
                return nativeCurrent.InstalledLocation.DateCreated;
            }
        }

        /// <summary>
        /// Gets the location of the installed package.
        /// </summary>
        /// <value>The location of the installed package.</value>
        [CLSCompliant(false)]
        public StorageFolder InstalledLocation
        {
            get
            {
                return nativeCurrent.InstalledLocation;
            }
        }

        /// <summary>
        /// Indicates whether this app is deployed as a private Beta through the store.
        /// </summary>
        public bool IsBeta
        {
            get;
            private set;
        }

#if !WINDOWS_APP
        private bool? isDevelopmentMode;
#endif
        /// <summary>
        /// Indicates whether the package is installed in development mode.
        /// </summary>
        /// <value>A Boolean value that indicates whether the package is installed in development mode.
        /// TRUE indicates that the package is installed in development mode; otherwise FALSE. </value>
        public bool IsDevelopmentMode
        {
            get
            {
#if WINDOWS_APP
                return nativeCurrent.IsDevelopmentMode;
#else
                if(!isDevelopmentMode.HasValue)
                {
#if SILVERLIGHT
                    // Side-loaded apps do not contain WMAppPRHeader license
                    System.Windows.Resources.StreamResourceInfo sri = System.Windows.Application.GetResourceStream(new Uri("WMAppPRHeader.xml", UriKind.Relative));
                    isDevelopmentMode = (sri == null);
#else
                    Task<StorageFile> t = Task.Run<StorageFile>(async () =>
                    {
                        try
                        {
                            return await Windows.Storage.StorageFile.GetFileFromApplicationUriAsync(new Uri("ms-appx:///AppxSignature.p7x"));
                        }
                        catch
                        {
                            return null;
                        }
                    });

                    t.Wait();
                    isDevelopmentMode = (t.Result == null);
#endif
                }

                return isDevelopmentMode.Value;
#endif
            }
        }

        /// <summary>
        /// Gets the logo of the package.
        /// </summary>
        public Uri Logo
        { 
            get; 
            private set; 
        }

        /// <summary>
        /// Gets the publisher display name of the package.
        /// </summary>
        public string PublisherDisplayName { get; private set; }

#if !WINDOWS_APP
        /// <summary>
        /// Returns a token that can be used to retrieve the thumbnail image associated with this application package.
        /// </summary>
        /// <returns>A token that can be used to retrieve the thumbnail image associated with this application package.</returns>
        /// <remarks>Use the GetSharedFileName method passing the token returned by this method to retrieve the thumbnail image associated with this application package.</remarks>
        public string GetThumbnailToken()
        {
            return nativeCurrent.GetThumbnailToken();
        }
#endif

#if SILVERLIGHT
        private static Uri ImagePathToUri(System.Xml.XmlReader reader)
        {
            UriKind kind = bool.Parse(reader["IsRelative"]) ? UriKind.Relative : UriKind.Absolute;
            reader.MoveToElement();
            string path = reader.ReadElementContentAsString().Replace('\\','/');
            return new Uri(path, kind);
        }
#endif
    }
}